package cn.ilikes.tools.hbase.jpa.indexs.lucenes;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.CorruptIndexException;
import org.apache.lucene.index.IndexNotFoundException;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.Sort;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.search.TopScoreDocCollector;
import org.apache.lucene.search.TotalHitCountCollector;
import org.apache.lucene.util.Version;
import org.slf4j.Logger;

import cn.ilikes.tools.hbase.jpa.clients.nodes.Node;
import cn.ilikes.tools.hbase.jpa.indexs.IndexNullException;
import cn.ilikes.tools.hbase.jpa.indexs.Indexer;
import cn.ilikes.tools.hbase.jpa.indexs.IndexingException;
import cn.ilikes.tools.hbase.jpa.metadatas.IEntityMetaData;
import cn.ilikes.tools.logs.Loggerfactory;
import cn.ilikes.tools.untils.PropertyAccessorHelper;

/**
 * 
 * @author fack
 * 
 */
public abstract class LuceneIndexer implements ILuceneIndexer {

	private FlushType flushType;

	private static Logger logger = Loggerfactory.instance(LuceneIndexer.class);

	public LuceneIndexer(FlushType flushType) {
		super();
		this.flushType = flushType;

	}

	private enum IndexAction {
		insert, update, remove
	}

	protected abstract boolean isEmptyDir();

	protected abstract Analyzer getAnalyzer();

	private LuceneEntityClassToDocument luceneEntityClassToDocument = new LuceneEntityClassToDocument();

	private LuceneEntityFieldsToDocuments luceneEntityFieldsToDocuments = new LuceneEntityFieldsToDocuments();

	@Override
	public void create(Node node) throws LuceneIndexingException {
		if (entityExistsByEntity(node)) {
			Object id = PropertyAccessorHelper.getId(Node.getEntity(node), Node.getEntityMetaData(node));
			removeById(Node.getEntityMetaData(node), id);
		}

		index(node);

	}

	protected void commits() {
		if (Loggerfactory.isdebug(logger))
			Loggerfactory.info(logger, " lucene index  on  commit  " + flushType);
		if (flushType.equals(FlushType.manual))
			return;

		flush();

	}

	protected abstract void commitFSD();

	@Override
	public void close() {
		IndexWriter indexWriter = createIndexWriter();
		close(indexWriter);
	}

	protected void close(IndexWriter indexWriter) {
		try {
			indexWriter.optimize();
			indexWriter.commit();
			indexWriter.close();
		} catch (CorruptIndexException e) {
			throw new LuceneIndexingException("create Index Reader  no success ! ", e);
		} catch (IOException e) {
			throw new LuceneIndexingException("create Index Reader  no success ! ", e);
		}

	}

	@Override
	public void flush() {
		if (Loggerfactory.isdebug(logger))
			Loggerfactory.info(logger, " lucene index  on  flush  ");
		IndexWriter indexWriter = createIndexWriter();
		try {
			indexWriter.optimize();
			indexWriter.commit();
			commitFSD();
		} catch (CorruptIndexException e) {
			throw new LuceneIndexingException("create Index Reader  no success ! ", e);
		} catch (IOException e) {
			throw new LuceneIndexingException("create Index Reader  no success ! ", e);
		}
	}

	public void close(IndexReader reader) {
		try {
			if (reader != null)
				reader.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	@Override
	public boolean entityExistsById(Class<?> entityClass, Object id) throws LuceneIndexingException {
		try {
			long total = selectTotalByEntityId(entityClass, id);
			if (total == 0)
				return false;
			return true;
		} catch (IndexNullException e) {
			e.printStackTrace();
			return false;
		}
	}

	@Override
	public boolean entityExistsByEntity(Node node) throws LuceneIndexingException {
		if (isEmptyDir())
			return false;
		node.getEntityMetaData();
		Object id = PropertyAccessorHelper.getId(Node.getEntity(node), Node.getEntityMetaData(node));
		return entityExistsById(Node.getEntityMetaData(node).getEntityClazz(), id);
	}

	@Override
	public long selectTotalByEntity(Node node) throws IndexingException {
		Object id = PropertyAccessorHelper.getId(Node.getEntity(node), Node.getEntityMetaData(node));
		return selectTotalByEntityId(Node.getEntityMetaData(node).getEntityClazz(), id);
	}

	@Override
	public long selectTotalByEntityId(Class<?> entityClass, Object id) throws IndexNullException, LuceneIndexingException {
		if (Loggerfactory.isdebug(logger))
			Loggerfactory.debug(logger, "select entity  index total    : " + entityClass.getClass().getName() + "  id : " + id);
		IndexReader reader = null;
		try {
			reader = createIndexReader();
			if (reader == null)
				throw new IndexNullException("create index reader is null ");
			IndexSearcher searcher = new IndexSearcher(reader);
			Term term = new Term(Indexer.KUNDERA_ID_FIELD, LuceneQueryUtils.getJPAHBaseKey(entityClass, id));
			TermQuery query = new TermQuery(term);
			TotalHitCountCollector counter = new TotalHitCountCollector();
			searcher.search(query, counter);
			if (Loggerfactory.isdebug(logger))
				Loggerfactory.debug(logger, "entity exists i ndex : " + counter.getTotalHits());
			return counter.getTotalHits();
		} catch (IndexNotFoundException e) {
			throw new IndexNullException(" reader Index   no success !  index not find ", e);
		} catch (IOException e) {
			throw new IndexNullException(" reader Index   no success ! ", e);
		}

		finally {
			close(reader);
		}
	}

	@Override
	public void remove(Node node) throws IndexingException {
		Object id = PropertyAccessorHelper.getId(Node.getEntity(node), Node.getEntityMetaData(node));
		removeById(Node.getEntityMetaData(node), id);
	}

	@Override
	public void removeById(IEntityMetaData metadata, Object id) throws LuceneIndexingException {

		String kunderaId = LuceneQueryUtils.getJPAHBaseKeyofId(metadata, id);

		if (Loggerfactory.isdebug(logger))
			Loggerfactory.debug(logger, "remove  index  @Entity[" + metadata.getEntityClazz().getSimpleName() + "] for kunderaId :" + kunderaId);
		try {
			IndexWriter w = createIndexWriter();
			w.deleteDocuments(new Term(Indexer.KUNDERA_ID_FIELD, kunderaId));
			commits();
		} catch (CorruptIndexException e) {
			throw new LuceneIndexingException("create Index Reader  no success ! ", e);
		} catch (IOException e) {
			throw new LuceneIndexingException("create Index Reader  no success ! ", e);
		}
	}

	public void removeAllIndexByEntity(Class<?> entityEntiity) throws IndexingException {

		if (Loggerfactory.isdebug(logger))
			Loggerfactory.debug(logger, "remove all  index  @Entity[" + entityEntiity);
		try {
			IndexWriter w = createIndexWriter();
			w.deleteDocuments(new Term(Indexer.ENTITY_CLASS_FIELD, entityEntiity.getCanonicalName().toLowerCase()));
			commits();
		} catch (CorruptIndexException e) {
			throw new LuceneIndexingException("create Index Reader  no success ! ", e);
		} catch (IOException e) {
			throw new LuceneIndexingException("create Index Reader  no success ! ", e);
		}
	}

	@Override
	public void index(Node node) throws LuceneIndexingException {
		if (!Node.getEntity(node).getClass().equals(Node.getEntityMetaData(node).getEntityClazz()))
			throw new LuceneIndexingException("entity object class : " + Node.getEntity(node).getClass() + "  not is   tEntityClazz :"
					+ Node.getEntityMetaData(node).getEntityClazz());
		if (Loggerfactory.isdebug(logger))
			Loggerfactory.debug(logger, " index  @Entity[" + Node.getEntityMetaData(node).getEntityClazz().getSimpleName() + "] for entity :" + Node.getEntity(node));
		Document document = indexLuceneDocument(node);
		indexDocument(Node.getEntityMetaData(node), document, Node.getEntity(node), IndexAction.insert);

	}

	@Override
	public void update(Node node) throws LuceneIndexingException {
		if (Loggerfactory.isdebug(logger))
			Loggerfactory.debug(logger, " update index   @Entity[" + Node.getEntityMetaData(node).getEntityClazz().getSimpleName() + "] for entity :" + Node.getEntity(node));
		Document document = indexLuceneDocument(node);
		indexDocument(Node.getEntityMetaData(node), document, Node.getEntity(node), IndexAction.update);

	}

	protected Document indexLuceneDocument(Node node) throws LuceneIndexingException {
		if (Loggerfactory.isdebug(logger))
			Loggerfactory.debug(logger, "index document @Entity  [" + Node.getEntityMetaData(node).getEntityClazz().getName() + "] ");

		if (!Node.getEntityMetaData(node).getEntityIndex().isIndexable()) {
			throw new LuceneIndexingException(Node.getEntityMetaData(node).getEntityClazz() + " can not index  set @index   true");
		}

		Document currentDoc = defults_column_family(node);
		return currentDoc;

	}

	@Override
	public <V> List<V> search(String luceneQuery, int start, int count, IEntityMetaData metadata, boolean fetchRelation) throws LuceneIndexingException {

		IndexReader reader = createIndexReader();
		if (reader == null)
			throw new LuceneIndexingException("Index reader is not initialized!");
		if (Loggerfactory.isdebug(logger))
			Loggerfactory.debug(logger, "Searching index with query[" + luceneQuery + "], start:" + start + ", count:" + count + "  fetchRelation : " + fetchRelation);
		List<V> indexKeyId = new ArrayList<V>();
		IndexSearcher searcher = new IndexSearcher(reader);
		QueryParser qp = new QueryParser(Version.LUCENE_34, Indexer.KUNDERA_ID_FIELD, this.getAnalyzer());
		try {
			qp.setLowercaseExpandedTerms(false);
			qp.setAllowLeadingWildcard(true);
			Query q = qp.parse(luceneQuery);
			Sort sort = new Sort();
			int selectCount = count;

			if (selectCount == ILuceneIndexer.INVALID) {
				TotalHitCountCollector counter = new TotalHitCountCollector();
				searcher.search(q, counter);
				int r = counter.getTotalHits();
				if (r == 0)
					return null;
				selectCount = r;
			}

			if (start == ILuceneIndexer.INVALID)
				start = 0;
			TopScoreDocCollector collector = TopScoreDocCollector.create(start + selectCount, false);
			searcher.search(q, collector);
			TopDocs docs = collector.topDocs(start, selectCount);
			for (ScoreDoc sc : docs.scoreDocs) {
				Document doc = searcher.doc(sc.doc);
				@SuppressWarnings("unchecked")
				V entityId = (V) doc.get(Indexer.ENTITY_ID_FIELD);
				indexKeyId.add(entityId);
			}
			if (Loggerfactory.isdebug(logger))
				Loggerfactory.debug(logger, "Searching index with query  [" + luceneQuery + "], start:" + start + ", count:" + selectCount + "  docs " + docs.totalHits
						+ "  indexKeyId : " + indexKeyId);
			return indexKeyId;
		} catch (ParseException e) {
			throw new LuceneIndexingException("Error while parsing Lucene Query " + luceneQuery, e);
		} catch (IOException e) {
			throw new LuceneIndexingException(e);
		} finally {
			close(reader);
		}

	}

	@Override
	public <V> List<V> search(String luceneQuery, int start, int count, IEntityMetaData metadata) throws LuceneIndexingException {
		return search(luceneQuery, start, count, metadata, false);
	}

	private Document defults_column_family(Node node) {

		if (Loggerfactory.isdebug(logger))
			Loggerfactory.debug(logger, "defults column  family   [  metadata : " + node.getEntityMetaData() + "], entity:" + node.getEntity());

		Document currentDoc = new Document();

		// Add entity class, PK info into document
		luceneEntityClassToDocument.addEntityClassToDocument(currentDoc, node);

		// Add all entity fields(columns) into document
		luceneEntityFieldsToDocuments.addEntityFieldsToDocumentst(currentDoc, node);
		return currentDoc;
	}

	private void indexDocument(IEntityMetaData metadata, Document document, Object entity, IndexAction action) {
		if (Loggerfactory.isdebug(logger))
			Loggerfactory.debug(logger, "Indexing document: " + document + " for in file system using Lucene");

		IndexWriter w = createIndexWriter();
		try {
			switch (action) {
			case insert:
				if (Loggerfactory.isdebug(logger))
					Loggerfactory.debug(logger, "Lucene indexing insert : " + entity.toString());
				w.addDocument(document);
				break;
			case update: {
				Term trem = new Term(Indexer.KUNDERA_ID_FIELD, LuceneQueryUtils.getJPAHBaseKeyofEntity(metadata, entity));
				if (Loggerfactory.isdebug(logger))
					Loggerfactory.debug(logger, "Lucene indexing update  : " + LuceneQueryUtils.getJPAHBaseKeyofEntity(metadata, entity));
				w.updateDocument(trem, document);
			}
				break;

			default:
				break;
			}
			// w.setR
			// w.optimize();
			// w.commit();
			// w.close();
			commits();
		} catch (CorruptIndexException e) {
			Loggerfactory.error(logger, "Error while indexing document " + document + " into Lucene. Details:" + e.getMessage());
			throw new LuceneIndexingException("Error while indexing document " + document + " into Lucene.", e);
		} catch (IOException e) {
			Loggerfactory.error(logger, "Error while indexing document " + document + " into Lucene. Details:" + e.getMessage());
			throw new LuceneIndexingException("Error while indexing document " + document + " into Lucene.", e);
		}
	}

	protected abstract IndexWriter createIndexWriter();

	protected abstract IndexReader createIndexReader() throws LuceneIndexingException;

}
